//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#include "WeShaderProg.h"

namespace WackiEngine
{

	//-----------------------------------------------------------------------------
	//! Default path for shader files used when only filename is passed in load.
	String ShaderProg::defaultPath = "../data/shaders";

	//-----------------------------------------------------------------------------
	// Error Strings defined in Shader.h
	extern char* aGLWEErrorString[];

	//-----------------------------------------------------------------------------
	//! Ctor with a vertex and a fragment shader filename
	ShaderProg::ShaderProg(const String& vertShaderFile, const String& fragShaderFile)
	{
		_isLinked = false;
		_programObjectGL = 0;


		// optional load vertex and/or fragment shaders
		addShader(new Shader(vertShaderFile, VertexShader));
		addShader(new Shader(fragShaderFile, FragmentShader));
	}

	//-----------------------------------------------------------------------------
	//! The destructor detaches all shader objects and deletes them
	ShaderProg::~ShaderProg()
	{
		//WE_LOG("~ShaderProg\n");

		for(uint32 i = 0; i<_shaderList.size(); i++)
		{
			if(_isLinked)
				glDetachShader(_programObjectGL, _shaderList[i]->_shaderObjectGL);

			// always delete shader objects before program object
			delete _shaderList[i];
		}

		if(_programObjectGL>0)
			glDeleteProgram(_programObjectGL);
	}
	//-----------------------------------------------------------------------------
	//! ShaderProg::addShader adds a shader to the shader list
	void ShaderProg::addShader(Shader* shader)
	{
		assert(shader);
		_shaderList.push_back(shader);
	}
	//-----------------------------------------------------------------------------
	/*! ShaderProg::init creates the OpenGL shaderprogram object, compiles all
	shader objects and attaches them to the shaderprogram. At the end all shaders
	are linked. If a shader fails to compile a simple texture only shader is
	compiled that shows an error message in the texture.
	*/
	void ShaderProg::init()
	{
		// create program object if it doesn't exist
		if(!_programObjectGL) _programObjectGL = glCreateProgram();

		// if already linked, detach, recreate and compile shaders
		if(_isLinked)
		{
			for(uint32 i = 0; i < _shaderList.size(); i++)
			{
				if(_isLinked)
				{
					glDetachShader(_programObjectGL, _shaderList[i]->_shaderObjectGL);
				}
			}
			_isLinked = false;
		}

		// compile all shader objects
		bool allSuccuessfullyCompiled = true;
		for(uint32 i = 0; i < _shaderList.size(); i++)
		{
			if(!_shaderList[i]->createAndCompile())
			{
				allSuccuessfullyCompiled = false;
				break;
			}
		}

		// try to compile alternative per vertex lighting shaders
		if(!allSuccuessfullyCompiled)
		{
			// delete all shaders and uniforms that where attached
			for(uint32 i = 0; i < _shaderList.size(); i++) delete _shaderList[i];
			_shaderList.clear();

			addShader(new Shader(defaultPath + "ErrorTex.vert", VertexShader));
			addShader(new Shader(defaultPath + "ErrorTex.frag", FragmentShader));

			allSuccuessfullyCompiled = true;
			for(uint32 i = 0; i < _shaderList.size(); i++)
			{
				if(!_shaderList[i]->createAndCompile())
				{
					allSuccuessfullyCompiled = false;
					break;
				}
			}
		}

		// attach all shader objects
		if(allSuccuessfullyCompiled)
		{
			for(uint32 i = 0; i < _shaderList.size(); i++)
			{
				glAttachShader(_programObjectGL, _shaderList[i]->_shaderObjectGL);
			}
		}

		int linked;
		glLinkProgram(_programObjectGL);
		glGetProgramiv(_programObjectGL, GL_LINK_STATUS, &linked);

		if(linked)
		{
			_isLinked = true;
			for(uint32 i = 0; i < _shaderList.size(); i++)
				_name += "+" + _shaderList[i]->name();
			//WE_LOG("Linked: %s", _name.c_str());
		}
		else
		{
			char log[256];
			glGetProgramInfoLog(_programObjectGL, sizeof(log), 0, &log[0]);
		}
	}
	//-----------------------------------------------------------------------------
	/*! ShaderProg::useProgram inits the first time the program and then uses it.
	Call this initialization if you pass your own custom uniform variables.
	*/
	void ShaderProg::useProgram()
	{
		if(_programObjectGL == 0 && _shaderList.size() > 0) init();

		if(_isLinked)
		{
			glUseProgram(_programObjectGL);
		}
	}
	//-----------------------------------------------------------------------------
	/*! ShaderProg::beginUse starts using the shaderprogram and transfers the
	the standard light and material parameter as uniform variables. It also passes
	the custom uniform variables of the _uniform1fList as well as the texture names.
	*/
	void ShaderProg::beginUse()
	{
		if(_programObjectGL == 0 && _shaderList.size() > 0) init();

		if(_isLinked)
		{
			// 1: Activate the shader program object
			glUseProgram(_programObjectGL);

		}
	}
	//----------------------------------------------------------------------------- 
	//! ShaderProg::endUse stops the shaderprogram
	void ShaderProg::endUse()
	{
		glUseProgram(0);
	}
	//-----------------------------------------------------------------------------
	/*! ShaderProg::getUniformLocation return the location id of a uniform
	variable. For not querying this with OpenGLs glGetUniformLocation we put all
	uniform locations into a hash map. glGet* function should be called only once
	during shader initialization and not during frame rendering because glGet*
	function force a pipeline flush.
	*/
	int ShaderProg::getUniformLocation(const char *name)
	{
		int loc;
		LocMap::iterator it = _uniformLocHash.find(name);
		if(it == _uniformLocHash.end())
		{
			// If not found query it from GL and add it to the hash map
			loc = glGetUniformLocation(_programObjectGL, name);
			_uniformLocHash[name] = loc;
		}
		else
			loc = it->second;

		return loc;
	}
	//-----------------------------------------------------------------------------
	/*! ShaderProg::getAttribLocation return the location id of a attribute
	variable. For not querying this with OpenGLs glGetAttribLocation we put all
	attribute locations into a hash map. glGet* function should be called only once
	during shader initialization and not during frame rendering because glGet*
	function force a pipeline flush.
	*/
	int ShaderProg::getAttribLocation(const char *name)
	{
		int loc;
		LocMap::iterator it = _attribLocHash.find(name);
		if(it == _attribLocHash.end())
		{
			// If not found query it from GL and add it to the hash map
			loc = glGetAttribLocation(_programObjectGL, name);
			_attribLocHash[name] = loc;
		}
		else
			loc = it->second;

		return loc;
	}
	//----------------------------------------------------------------------------- 


	//-----------------------------------------------------------------------------
	//! Passes the float value v0 to the uniform variable "name"
	int ShaderProg::uniform1f(const char *name, float v0)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform1f(loc, v0);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes the float values v0 & v1 to the uniform variable "name"
	int ShaderProg::uniform2f(const char *name, float v0, float v1)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform2f(loc, v0, v1);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes the float values v0, v1 & v2 to the uniform variable "name"
	int ShaderProg::uniform3f(const char *name, float v0, float v1, float v2)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform3f(loc, v0, v1, v2);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes the float values v0,v1,v2 & v3 to the uniform variable "name"
	int ShaderProg::uniform4f(const char *name, float v0, float v1, float v2, float v3)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform4f(loc, v0, v1, v2, v3);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes the int values v0 to the uniform variable "name"
	int ShaderProg::uniform1i(const char *name, int v0)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform1i(loc, v0);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes the int values v0 & v1 to the uniform variable "name"
	int ShaderProg::uniform2i(const char *name, int v0, int v1)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform2i(loc, v0, v1);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes the int values v0, v1 & v2 to the uniform variable "name"
	int ShaderProg::uniform3i(const char *name, int v0, int v1, int v2)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform3i(loc, v0, v1, v2);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes the int values v0, v1, v2 & v3 to the uniform variable "name"
	int ShaderProg::uniform4i(const char *name, int v0, int v1, int v2, int v3)
	{
		int loc = getUniformLocation(name);
		if(loc == -1) return false;
		glUniform4i(loc, v0, v1, v2, v3);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes 1 float value py pointer to the uniform variable "name"
	int ShaderProg::uniform1fv(const char *name, int count, const float* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform1fv(loc, count, value);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes 2 float values py pointer to the uniform variable "name"
	int ShaderProg::uniform2fv(const char *name, int count, const float* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform2fv(loc, count, value);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes 3 float values py pointer to the uniform variable "name"
	int ShaderProg::uniform3fv(const char *name, int count, const float* value)
	{
		int loc = getUniformLocation(name);
		if(loc == -1) return false;
		glUniform3fv(loc, count, value);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes 4 float values py pointer to the uniform variable "name"
	int ShaderProg::uniform4fv(const char *name, int count, const float* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform4fv(loc, count, value);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes 1 int value py pointer to the uniform variable "name" 
	int ShaderProg::uniform1iv(const char *name, int count, const int* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform1iv(loc, count, value);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes 2 int values py pointer to the uniform variable "name"  
	int ShaderProg::uniform2iv(const char *name, int count, const int* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform2iv(loc, count, value);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes 3 int values py pointer to the uniform variable "name"	
	int ShaderProg::uniform3iv(const char *name, int count, const int* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform3iv(loc, count, value);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes 4 int values py pointer to the uniform variable "name"	
	int ShaderProg::uniform4iv(const char *name, int count, const int* value)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniform4iv(loc, count, value);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes a 2x2 float matrix values py pointer to the uniform variable "name"  
	int ShaderProg::uniformMatrix2fv(const char *name, int count, const float* value, GLboolean transpose)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniformMatrix2fv(loc, count, transpose, value);

		return loc;
	}
	//-----------------------------------------------------------------------------
	//! Passes a 3x3 float matrix values py pointer to the uniform variable "name"	
	int ShaderProg::uniformMatrix3fv(const char *name, int count, const float* value, GLboolean transpose)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniformMatrix3fv(loc, count, transpose, value);

		return loc;
	}
	//----------------------------------------------------------------------------- 
	//! Passes a 4x4 float matrix values py pointer to the uniform variable "name"  
	int ShaderProg::uniformMatrix4fv(const char *name, int count, const float* value, GLboolean transpose)
	{
		int loc = getUniformLocation(name);
		if(loc >= 0) glUniformMatrix4fv(loc, count, transpose, value);

		return loc;
	}
	//----------------------------------------------------------------------------- 

}